﻿namespace TestApp.Client
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Reactive.Concurrency;
    using System.Reactive.Disposables;
    using System.Reactive.Linq;
    using System.ServiceModel;
    using System.Threading;
    using Contracts;
    using JetBrains.Annotations;

    internal sealed class DataServiceAgent : IDataServiceAgent
    {
        private readonly IScheduler _scheduler;

        public DataServiceAgent([NotNull] IScheduler scheduler)
        {
            Contract.Requires<ArgumentNullException>(scheduler != null);
            _scheduler = scheduler;
        }

        public IObservable<IEnumerable<EntityDto>> CreateEntitySource()
        {
            Contract.Ensures(Contract.Result<IObservable<IEnumerable<EntityDto>>>() != null);
            return Observable.Create<IEnumerable<EntityDto>>(observer => Subscribe(observer))
                .Retry()
                .SubscribeOn(_scheduler)
                .Publish()
                .RefCount();
        }

        [NotNull] private static IDisposable Subscribe([NotNull] IObserver<IEnumerable<EntityDto>> observer)
        {
            Contract.Requires<ArgumentNullException>(observer != null);
            Contract.Ensures(Contract.Result<IDisposable>() != null);
            Timer timer;
            var callback = new DataServiceCallback(observer.OnNext);
            var channelFactory = new DuplexChannelFactory<IDataService>(
                callback, 
                new NetTcpBinding
                {
                    SendTimeout = TimeSpan.FromSeconds(5),
                    OpenTimeout = TimeSpan.FromSeconds(5),
                    CloseTimeout = TimeSpan.FromSeconds(5)
                }, new EndpointAddress(new Uri("net.tcp://localhost:8000/")));
            try
            {
                channelFactory.Open(TimeSpan.FromSeconds(15));
                var dataService = channelFactory.CreateChannel();
                var subscriptionId = dataService.SubscribeToAll();
                timer = new Timer(o => Ping(observer, dataService, subscriptionId), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
            }
            catch
            {
                channelFactory.Close();
                throw;
            }

            return Disposable.Create(() =>
            {
                try
                {
                    timer.Dispose();
                    channelFactory.Close();
                }
                // ReSharper disable once EmptyGeneralCatchClause
                catch
                {
                }
            });
        }

        private static void Ping([NotNull] IObserver<IEnumerable<EntityDto>> observer, [NotNull] IDataService dataService, Guid subscriptionId)
        {
            Contract.Requires<ArgumentNullException>(observer != null);
            Contract.Requires<ArgumentNullException>(dataService != null);
            try
            {
                dataService.Ping(subscriptionId);
            }
            catch (Exception ex)
            {
                observer.OnError(ex);
            }
        }

        private sealed class DataServiceCallback : IDataServiceCallback
        {
            private readonly Action<IEnumerable<EntityDto>> _onNext;

            public DataServiceCallback([NotNull] Action<IEnumerable<EntityDto>> onNext)
            {
                Contract.Requires<ArgumentNullException>(onNext != null);
                _onNext = onNext;
            }

            public void ProcessEntities(IEnumerable<EntityDto> enities)
            {
                _onNext(enities);
            }

            public void Ping()
            {
            }
        }
    }
}
